package indigo.mybatis.dao.criterion;

import static java.util.Objects.requireNonNull;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

public class Junction extends Criterion {
	private static final long serialVersionUID = 2376755259271286711L;

	private static final FlattenPart BEGIN_PART = new JunctionFlattenPart("BEGIN");
	private static final FlattenPart END_PART = new JunctionFlattenPart("END");
	private static final FlattenPart AND_PART = new JunctionFlattenPart("AND");
	private static final FlattenPart OR_PART = new JunctionFlattenPart("OR");

	private final Logical logical;
	private final List<Criterion> criteria;

	public Junction(Logical logical, Criterion... criteria) {
		this(logical, Arrays.asList(criteria));
	}

	public Junction(Logical logical, Collection<? extends Criterion> criteria) {
		this.logical = requireNonNull(logical, "logical cannot be null");
		this.criteria = checkCriteria(requireNonNull(criteria, "criteria cannot be null"), logical);
	}

	@Override
	public List<FlattenPart> flatten() {
		FlattenPart connector = getLogical() == Logical.OR ? OR_PART : AND_PART;
		List<FlattenPart> parts = Lists.newArrayList();
		for (Criterion criterion : getCriteria()) {
			List<FlattenPart> childParts = criterion.flatten();
			if (!childParts.isEmpty()) {
				if (!parts.isEmpty()) {
					parts.add(connector);
				}
				parts.addAll(childParts);
			}
		}
		List<FlattenPart> finalParts = Lists.newArrayList();
		if (!parts.isEmpty()) {
			finalParts.add(BEGIN_PART);
			finalParts.addAll(parts);
			finalParts.add(END_PART);
		}
		return ImmutableList.copyOf(finalParts);
	}

	@Override
	public String toString() {
		return String.format("Junction {logical=%s, criteria=%s}", logical.name(), criteria.toString());
	}

	public Logical getLogical() {
		return logical;
	}

	public List<Criterion> getCriteria() {
		return criteria;
	}

	public static enum Logical {
		AND, OR
	}

	private static List<Criterion> checkCriteria(Collection<? extends Criterion> criteria, Logical rootLogical) {
		List<Criterion> result = Lists.newArrayList();
		for (Criterion item : criteria) {
			if (item instanceof Junction) {
				Junction junction = (Junction) item;
				Junction.Logical logical = junction.getLogical();
				List<Criterion> childCriteria = junction.getCriteria();

				if (childCriteria.size() > 0) {
					if (childCriteria.size() == 1 || logical == rootLogical) {
						result.addAll(childCriteria);
					} else {
						result.add(junction);
					}
				}
			} else {
				result.add(item);
			}
		}
		return ImmutableList.copyOf(result);
	}

	private static class JunctionFlattenPart extends FlattenPart {
		public JunctionFlattenPart(String instruction) {
			super(Type.JUNCTION, instruction);
		}
	}
}
